package com.webex.ta.hydra.plugin;

import com.webex.ta.hydra.CoreClassLoader;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Created by Cisco WebEx.
 * User: vegaz
 * Date: 2010-9-27
 * Time: 15:20:27
 */
public class PluginJarClassLoader extends ClassLoader {
    private static Map<String, Object> classHash = new Hashtable<String, Object>();
    private static Map<String, Object> resourcesHash = new HashMap<String, Object>();

    private PluginJar jar;
    private String path;
    private ZipFile zip;


    public PluginJarClassLoader(CoreClassLoader parent) {
        this(parent, null);
    }

    public PluginJarClassLoader(CoreClassLoader parent, PluginJar jar) {
        super(parent);
        this.jar = jar;
        if (jar != null)
            this.path = jar.getPath();
    }

    public URL getResource(String name) {
        try {
            if (jar != null) {
                ZipEntry entry = zip.getEntry(name);
                if (entry != null) {
                    return new URL(getResourceAsPath(name));
                }
            }

            Object obj = resourcesHash.get(name);
            if (obj instanceof PluginJarClassLoader) {
                PluginJarClassLoader classLoader = (PluginJarClassLoader) obj;
                if (classLoader != this)
                    return classLoader.getResource(name);
            } else {
                URL ret = getSystemResource(name);
//                if (ret != null) {
//
//                }
                return ret;
            }
        } catch (IOException io) {
            // ignore
        }
        return null;
    }

    public String getResourceAsPath(String name) {
        // this must be fixed during plugin development
        if (jar == null)
            throw new UnsupportedOperationException(
                    "don't call getResourceAsPath() on anonymous JARClassLoader");

        if (!name.startsWith("/"))
            name = '/' + name;

//        return "jeditresource:/" + MiscUtilities.getFileName(
//                jar.getZipFile().getName()) + '!' + name;
        try {
            URL url = new URL("file:/" + jar.getPath());
            return "jar:file:" + url.getFile() + "!" + name;
        } catch (MalformedURLException e) {
            return "jar:file:/" + jar.getPath() + "!" + name;
        }

//        return "jar:file:/" + jar.getPath() + "!" + name;
    }

    public Enumeration getResources(String name) throws IOException {
        class SingleElementEnumeration implements Enumeration {
            private Object element;

            SingleElementEnumeration(Object element) {
                this.element = element;
            }

            public boolean hasMoreElements() {
                return element != null;
            }

            public Object nextElement() {
                if (element != null) {
                    Object retval = element;
                    element = null;
                    return retval;
                } else
                    throw new NoSuchElementException();
            }
        }

        URL resource = getResource(name);
        return new SingleElementEnumeration(resource);
    }

    public InputStream getResourceAsStream(String name) {
        try {
            // try in current jar first
            if (jar != null) {
                ZipEntry entry = zip.getEntry(name);
                if (entry != null) {
                    return zip.getInputStream(entry);
                }
            }
        } catch (Exception ignore) {}

        try {
            // then try from another jar
            Object obj = resourcesHash.get(name);
            if (obj instanceof PluginJarClassLoader) {
                PluginJarClassLoader classLoader = (PluginJarClassLoader) obj;
                return classLoader.getResourceAsStream(name);
            }
            // finally try from the system class loader
            return getSystemResourceAsStream(name);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public Class loadClass(String clazz, boolean resolveIt) throws ClassNotFoundException {
        Object obj = classHash.get(clazz);
//		if(obj == NO_CLASS)
//		{
//			// we remember which classes we don't exist
//			// because BeanShell tries loading all possible
//			// <imported prefix>.<class name> combinations
//			throw new ClassNotFoundException(clazz);
//		}
        try {
        if (obj instanceof PluginJarClassLoader) {
            PluginJarClassLoader classLoader = (PluginJarClassLoader) obj;
//            System.out.println("PluginJar " + classLoader.zip.getName() +" loaded: " + clazz);
            return classLoader._loadClass(clazz, resolveIt);
        }
        } catch (ClassNotFoundException e) {
            // ignore
        }

        try {
            ClassLoader parent = this.getParent();
            Class cls = null;
            if (parent instanceof CoreClassLoader) {
                cls = ((CoreClassLoader)parent)._loadClass(clazz, resolveIt); 
            } else {
                cls = parent.loadClass(clazz);
                if (resolveIt)
                    resolveClass(cls);
            }
//            System.out.println("Core loaded: " + clazz);
            return cls;
        } catch (ClassNotFoundException e) {
            // ignore
        }
        throw new ClassNotFoundException(clazz);
    }

    protected synchronized Class _loadClass(String clazz, boolean resolveIt)
            throws ClassNotFoundException {
        synchronized (this) {
            Class cls = findLoadedClass(clazz);
            if (cls != null) {
                if (resolveIt)
                    resolveClass(cls);
                return cls;
            }

            String name = classToFile(clazz);

            try {
//                definePackage(clazz);
                ZipEntry entry = zip.getEntry(name);

                if (entry == null)
                    throw new ClassNotFoundException(clazz);

                InputStream in = zip.getInputStream(entry);

                int len = (int) entry.getSize();
                byte[] data = new byte[len];
                int success = 0;
                int offset = 0;
                while (success < len) {
                    len -= success;
                    offset += success;
                    success = in.read(data, offset, len);
                    if (success == -1) {
                        throw new ClassNotFoundException(clazz);
                    }
                }

                cls = defineClass(clazz, data, 0, data.length);

                if (resolveIt)
                    resolveClass(cls);

                return cls;
            } catch (IOException io) {
                throw new ClassNotFoundException(clazz);
            }
        }
    }

    public static String classToFile(String name) {
        return name.replace('.', '/').concat(".class");
    }

//    private void definePackage(String clazz) throws IOException
//	{
//		int idx = clazz.lastIndexOf('.');
//		if (idx != -1)
//		{
//			String name = clazz.substring(0, idx);
//			if (getPackage(name) == null) definePackage(name, new JarFile(jar.getFile()).getManifest());
//		}
//	}


    public void activate() throws IOException {
        if (zip != null)
            zip.close();
        zip = new ZipFile(path);
        String[] classes = jar.getClasses();
        if (classes != null) {
            for (String aClass : classes) {
                classHash.put(aClass, this);
            }
        }

        String[] resources = jar.getResources();
        if (resources != null) {
            for (String resource : resources) {
                resourcesHash.put(resource, this);
            }
        }
    }

    public void deactivate() throws IOException {
        zip.close();
        String[] classes = jar.getClasses();
        if (classes != null) {
            for (String aClass : classes) {
                classHash.remove(aClass);
            }
        }

        String[] resources = jar.getResources();
        if (resources != null) {
            for (String resource : resources) {
                resourcesHash.remove(resource);
            }
        }
    }
}
